﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Phoenix.ComponentModel;

namespace Phoenix.Framework
{
    public class ControllerAction : NotifyPropertyChanged
    {
        const string AssemblyControllerActionArguments = @"((?<Assembly>.*?);)?(?<Controller>.*?)\.(?<Action>[^\?]*)(\?(?<Arguments>.*))?";
        static readonly Regex controllerActionMatcher = new Regex(AssemblyControllerActionArguments, RegexOptions.Compiled);
        static readonly Dictionary<string, ControllerAction> controllerActionCache = new Dictionary<string, ControllerAction>(); 

        public string Assembly { get; internal set; }
        public string Controller { get; private set; }
        public string Action { get; private set; }
        public QueryStringArgumentsCollection QueryStringArguments { get; private set; }

        public ControllerAction(string controller, string action, string assembly, IEnumerable<KeyValuePair<string, string>> queryStringArguments = null)
        {
            Assembly = assembly;
            Controller = controller;
            Action = action;
            QueryStringArguments = new QueryStringArgumentsCollection(queryStringArguments);
        }

        public override string ToString()
        {
            var formatBuilder = new StringBuilder();
            if (!string.IsNullOrEmpty(Assembly))
                formatBuilder.AppendFormat("{0};", Assembly);

            formatBuilder.AppendFormat("{0}.{1}", Controller, Action);

            if (QueryStringArguments != null && QueryStringArguments.Count > 0)
            {
                formatBuilder.Append("?");

                foreach (var queryStringArgument in QueryStringArguments)
                {
                    formatBuilder.AppendFormat("{0}={1}", queryStringArgument.Key, queryStringArgument.Value);
                }
            }

            return formatBuilder.ToString();
        }

        public static ControllerAction CreateFrom(string fromCommandString, string fallbackAssembly = null)
        {
            var key = string.Concat(fromCommandString, fallbackAssembly);
            if (controllerActionCache.ContainsKey(key))
            {
                return controllerActionCache[key];
            }

            string action;
            string controller;
            string parsedAssembly;
            KeyValuePair<string, string>[] arguments;
            if (ParseCommand(fromCommandString, out parsedAssembly, out controller, out action, out arguments))
            {
                var controllerAction = new ControllerAction(controller, action, parsedAssembly ?? fallbackAssembly, arguments);
                controllerActionCache.Add(key, controllerAction);
                return controllerAction;
            }
            return null;
        }

        public static bool ParseCommand(string commandText,
            out string assembly, out string controller, out string action,
            out KeyValuePair<string, string>[] arguments)
        {
            if (commandText == null)
            {
                assembly = null;
                controller = null;
                action = null;
                arguments = new KeyValuePair<string, string>[] {};
                return false;
            }
            var match = controllerActionMatcher.Match(commandText);
            assembly = match.Groups["Assembly"].Value;
            if (string.IsNullOrEmpty(assembly))
                assembly = null;
            controller = match.Groups["Controller"].Value;
            action = match.Groups["Action"].Value;
            arguments = GetArguments(match.Groups["Arguments"].Value).ToArray();

            return match.Success;
        }

        static IEnumerable<KeyValuePair<string, string>> GetArguments(string value)
        {
            if (value != null)
            {
                var pairs = value.Split('&');
                foreach (var parts in pairs.Select(pair => pair.Split('=')).Where(parts => parts.Length == 2))
                {
                    yield return new KeyValuePair<string, string>(parts[0], parts[1]);
                }
            }
        }

        public bool Equals(ControllerAction other)
        {
            return 
                Equals(other.Assembly??string.Empty, Assembly??string.Empty) && 
                Equals(other.Controller, Controller) && 
                Equals(other.Action, Action);
        }

        public override bool Equals(object obj)
        {
            if (obj.GetType() != typeof (ControllerAction)) return false;
            return Equals((ControllerAction) obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                var result = (Assembly != null ? Assembly.GetHashCode() : 0);
                result = (result*397) ^ (Controller != null ? Controller.GetHashCode() : 0);
                result = (result*397) ^ (Action != null ? Action.GetHashCode() : 0);
                return result;
            }
        }

        public static bool operator ==(ControllerAction left, ControllerAction right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(ControllerAction left, ControllerAction right)
        {
            return !Equals(left, right);
        }

#if WINDOWS_PHONE
        public Uri ToDeepLink()
        {
            return new Uri(string.Format("/Shell.xaml?deepLink={0}", System.Net.HttpUtility.UrlEncode(ToString())), UriKind.Relative);
        }
#endif
    }
}